/**
 * RoleParser.java
 */
package com.rizzo.web.security;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Parser that parses the roles.xml (a file that maps the roles to the functions of that role).
 * It uses DOM to parse the XML into the known Model of Role-Function.
 * <p/>
 *
 * @author jalieven
 */
public class RoleParser {

    /**
     * Parses the roles.xml file into a List of {@link Role}.
     *
     * @param fileToParse The {@link java.io.File} that is to be parsed.
     * @return a List of {@link Role}.
     * @throws ParserConfigurationException
     * @throws RoleParserException
     * @throws SAXException
     * @throws IOException
     */
    public static List<Role> parseRoles(File fileToParse) throws ParserConfigurationException, RoleParserException, SAXException, IOException {
        List<Role> parsedRoles = new ArrayList<Role>();
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder parser = factory.newDocumentBuilder();
        Document document = parser.parse(fileToParse);
        NodeList roleNodeList = document.getElementsByTagName("role");
        for (int i = 0; i < roleNodeList.getLength(); i++) {
            Node roleNode = roleNodeList.item(i);
            NodeList roleChildNode = roleNode.getChildNodes();
            Node roleNameNode = roleChildNode.item(1);
            if (!"name".equals(roleNameNode.getNodeName())) {
                throw new RoleParserException("Error while parsing: " + fileToParse.getAbsolutePath() + ", " +
                        "expected tagname:name but was " + roleNameNode.getNodeName());
            }
            Node functionsNode = roleChildNode.item(3);
            if (!"functions".equals(functionsNode.getNodeName())) {
                throw new RoleParserException("Error while parsing: " + fileToParse.getAbsolutePath() + ", " +
                        "expected tagname:functions but was " + functionsNode.getNodeName());
            }
            // allowedNode is not mandatory
            List<Function> allowedList = null;
            Node allowedNode = roleChildNode.item(5);
            if (allowedNode != null) {
                if (!"allowed".equals(allowedNode.getNodeName())) {
                    throw new RoleParserException("Error while parsing: " + fileToParse.getAbsolutePath() + ", " +
                            "expected tagname:allowed but was " + allowedNode.getNodeName());
                }
                allowedList = getAllowedFromAllowedNode(allowedNode, fileToParse);
            }
            List<Function> functionsList = getFunctionsFromFunctionsNode(functionsNode, fileToParse);
            String roleName = roleNameNode.getTextContent();
            parsedRoles.add(new Role(RoleEnum.getEnumForAlias(roleName), functionsList, allowedList));
        }
        return parsedRoles;
    }

    /**
     * Private method that allows the parsing of the <allowed>-node portion of the roles.xml file.
     *
     * @param allowedNode
     * @param fileToParse
     * @return
     * @throws RoleParserException
     */
    private static List<Function> getAllowedFromAllowedNode(Node allowedNode, File fileToParse) throws RoleParserException {
        List<Function> functions = new ArrayList<Function>();
        NodeList allowedNodeList = allowedNode.getChildNodes();
        for (int i = 1; i < allowedNodeList.getLength(); i += 2) {
            Node allowNode = allowedNodeList.item(i);
            if (!"url".equals(allowNode.getNodeName())) {
                throw new RoleParserException("Error while parsing: " + fileToParse.getAbsolutePath() + ", " +
                        "expected tagname:url but was " + allowNode.getNodeName());
            }
            functions.add(new Function("ALLOWED", allowNode.getTextContent(), "ALL"));
        }
        return functions;
    }

    /**
     * Private method that allows the parsing of a <function>-node portion of the roles.xml file.
     *
     * @param functionsNode
     * @param fileToParse
     * @return
     * @throws RoleParserException
     */
    private static List<Function> getFunctionsFromFunctionsNode(Node functionsNode, File fileToParse) throws RoleParserException {
        List<Function> functions = new ArrayList<Function>();
        NodeList functionNodeList = functionsNode.getChildNodes();
        for (int i = 0; i < functionNodeList.getLength(); i++) {
            Node functionNode = functionNodeList.item(i);
            if ("function".equals(functionNode.getNodeName())) {
                NodeList functionChildNode = functionNode.getChildNodes();
                if (functionChildNode.getLength() < 5) {
                    throw new RoleParserException("Error while parsing: " + fileToParse.getAbsolutePath() + ", " +
                            "too few child nodes found in the function-node");
                }
                Node functionNameNode = functionChildNode.item(1);
                if (!"name".equals(functionNameNode.getNodeName())) {
                    throw new RoleParserException("Error while parsing: " + fileToParse.getAbsolutePath() + ", " +
                            "expected tagname:name but was " + functionNameNode.getNodeName());
                }
                Node functionUrlNode = functionChildNode.item(3);
                if (!"url".equals(functionUrlNode.getNodeName())) {
                    throw new RoleParserException("Error while parsing: " + fileToParse.getAbsolutePath() + ", " +
                            "expected tagname:url but was " + functionUrlNode.getNodeName());
                }
                Node functionPanelNode = functionChildNode.item(5);
                if (!"panel".equals(functionPanelNode.getNodeName())) {
                    throw new RoleParserException("Error while parsing: " + fileToParse.getAbsolutePath() + ", " +
                            "expected tagname:panel but was " + functionPanelNode.getNodeName());
                }
                functions.add(new Function(functionNameNode.getTextContent(), functionUrlNode.getTextContent(), functionPanelNode.getTextContent()));
            }
        }
        return functions;
    }

}
